#include "std.h"
#include "swap.h"
#include "file_incl.h"
#include "simul_efun.h"
#include "master.h"
#include "comm.h"
#include "md.h"
#include "file.h"
#include "port.h"

/*
 * Swap out programs from objects.
 *
 * Todo:
 *   Separate objects/programs, so that they can swapped out
 *   independently.  This way even inherited programs could
 *   be swapped, at the expense of not swapping entire program.
 */

static int num_swapped;
static int total_bytes_swapped;
static int line_num_bytes_swapped;
static char file_name_buf[100];
static char *file_name = file_name_buf;

/* The swap file is opened once */
#ifdef SWAP_USE_FD
static int swap_file;
#else
static FILE *swap_file;
#endif

#define DIFF(x, y) ((char *)(x) - (char *)(y))
#define ADD(x, y) (&(((char *)(y))[(POINTER_INT)x]))

typedef struct sw_block_s
{
	int start;
	int length;
	struct sw_block_s *next;
} sw_block_t;

static sw_block_t *swap_free;

static int last_data;

static int assert_swap_file(void);
static int alloc_swap(int);
static void free_swap(int start, int length);
static int swap_in(char **, int);
static int swap_out(char *, int, int *);

/**
 ** General swapping routines.
 **/

/*
 * Make sure swap file is opened.
 */
static int assert_swap_file()
{
	if (swap_file == NULL)
	{
#ifdef SWAP_USE_FD
		char host[50];

		gethostname(host, sizeof host);
		sprintf(file_name_buf, "%s.%s.%d", SWAP_FILE, host,
				external_port[0].port);
		file_name = file_name_buf;
		if (file_name[0] == '/')
		file_name++;
		swap_file = open(file_name, O_RDWR | O_CREAT | O_TRUNC);
#else
		char host[50];

		gethostname(host, sizeof host);
		sprintf(file_name_buf, "%s.%s.%d", SWAP_FILE, host,
		        external_port[0].port);
		file_name = file_name_buf;
		if (file_name[0] == '/')
			file_name++;
		swap_file = fopen(file_name, "w+b");
#endif
		swap_free = 0;
		last_data = 0;
		/* Leave this file pointer open ! */
		if (swap_file == 0)
			return 0;
	}
	return 1;
}

/*
 *  Seek the swap file
 */
static void swap_seek(long offset, int flag)
{
	int ret;

	do
	{
#ifdef SWAP_USE_FD
		ret = lseek(swap_file, offset, flag);
#else
		ret = fseek(swap_file, offset, flag);
#endif
	}
	while (ret == -1 && errno == EINTR);
	if (ret == -1)
		fatal("Couldn't seek the swap file, error %s, offset %d.\n",
		        port_strerror(errno), offset);
}

/*
 * Find a position to swap to, using free blocks if possible.
 * 'length' is the size we need.
 *
 * Todo - think about better free block allocation methods
 */
static int alloc_swap(int length)
{
	sw_block_t *ptr, *prev;
	int ret;

	/*
	 * Doing first fit.  (next fit might work nicely if next pointer is reset
	 * when a block is freed, anticipating a new allocation of the same size)
	 */
	for (ptr = swap_free, prev = 0; ptr; prev = ptr, ptr = ptr->next)
	{
		if (ptr->length < length)
			continue;
		/*
		 * found a block, update free list
		 */
		ret = ptr->start;
		ptr->start += length;
		ptr->length -= length;
		if (ptr->length == 0)
		{
			/*
			 * exact fit, remove free block from list
			 */
			if (!prev)
				swap_free = ptr->next;
			else
				prev->next = ptr->next;
			FREE(ptr);
		}
		return ret;
	}

	/*
	 * no appropriate blocks found, go to end of file
	 */
	return last_data;
}

/*
 * Free up a chunk of swap space, coalescing free blocks if necessary.
 *
 * Todo - think about tradeoff of storing the free block
 * info in the swap file itself.
 */
static void free_swap(int start, int length)
{
	sw_block_t *m, *ptr, *prev;

	length += sizeof(int); /* extend with size of hidden information */

	/*
	 * Construct and insert new free block
	 */
	m = (sw_block_t *) DXALLOC(sizeof(sw_block_t), TAG_SWAP, "free_swap");
	m->start = start;
	m->length = length;

	for (ptr = swap_free, prev = 0; ptr; prev = ptr, ptr = ptr->next)
	{
		if (start < ptr->start)
			break;
	}
	if (!prev)
	{
		swap_free = m;
	}
	else
	{
		prev->next = m;
	}
	m->next = ptr;

	/*
	 * Combine adjacent blocks
	 */
	if (ptr && m->start + m->length == ptr->start)
	{
		m->length += ptr->length;
		m->next = ptr->next;
		FREE(ptr);
	}
	if (prev && prev->start + prev->length == m->start)
	{
		prev->length += m->length;
		prev->next = m->next;
		FREE(m);
		m = prev;
	}
	/*
	 * There is an implicit infinite block at the end making life hard Can't
	 * do this earlier, since m and prev could have combined, so prev must be
	 * found again (or use doubly linked list, etc).
	 */
	if (m->start + m->length == last_data)
	{
		DEBUG_CHECK(m->next, "extraneous free swap blocks!\n");
		/* find prev pointer again *sigh* */
		for (ptr = swap_free, prev = 0; ptr != m; prev = ptr, ptr = ptr->next)
			;
		last_data = m->start;
		FREE(m);
		if (!prev)
			swap_free = 0;
		else
			prev->next = 0;
	}
}

/*
 * Actually swap something out.
 *   block   - the memory to swap out
 *   size    - how big it is
 *   locp    - the swap location is written to the int this points to
 */
static int swap_out(char * block, int size, int * locp)
{
	if (!block || time_to_swap == 0)
		return 0;
	if (!assert_swap_file())
		return 0;

	if (*locp == -1)
	{ /* needs data written out */
		*locp = alloc_swap(size + sizeof size);
		swap_seek(*locp, 0);
#ifdef SWAP_USE_FD
		if ((write(swap_file, &size, sizeof size) != sizeof size) ||
				write(swap_file, block, size) != size)
		{
			debug_perror("swap_out", swap_file);
			*locp = -1;
			return 0;
		}
#else
		if (fwrite((char *) &size, sizeof size, 1, swap_file) != 1
		        || fwrite(block, size, 1, swap_file) != 1)
		{
			debug_perror("swap_out:swap file", 0);
			*locp = -1;
			return 0;
		}
#endif
		if (*locp >= last_data)
			last_data = *locp + sizeof size + size;
	}
	total_bytes_swapped += size;/* also count sizeof int?? */
	return 1;
}

/*
 * Read something back in from swap.  Return the size.
 *   blockp    - a pointer to what will hold the block read in
 *   loc       - position in the swap file to read from
 */
static int swap_in(char ** blockp, int loc)
{
	int size;

	DEBUG_CHECK(!blockp, "blockp null in swap_in()\n");

	if (loc == -1)
		return 0;
	swap_seek(loc, 0);
#ifdef SWAP_USE_FD
	/* find out size */
	if (read(swap_file, &size, sizeof size) == -1)
	fatal("Couldn't read the swap file.\n");
	DEBUG_CHECK(size <= 0, "Illegal size read from swap file.\n");
	*blockp = DXALLOC(size, TAG_SWAP, "swap_in");
	if (read(swap_file, *blockp, size) == -1)
	fatal("Couldn't read the swap file.\n");
#else
	/* find out size */
	if (fread((char *) &size, sizeof size, 1, swap_file) == -1)
		fatal("Couldn't read the swap file.\n");
	DEBUG_CHECK(size <= 0, "Illegal size read from swap file.\n");
	*blockp = DXALLOC(size, TAG_SWAP, "swap_in");
	if (fread(*blockp, size, 1, swap_file) == -1)
		fatal("Couldn't read the swap file.\n");
#endif
	total_bytes_swapped -= size;
	return size;
}

/**
 ** Routines to swap/load specific things.
 **/

/*
 * marion - adjust pointers for swap out and later relocate on swap in
 *   program
 *   functions
 *   strings
 *   variable_names
 *   inherit
 *   argument_types
 *   type_start
 */
int locate_out(program_t * prog)
{
	if (!prog)
		return 0;debug(d_flag, ("locate_out: %p %p %p %p %p %p %p\n",
					prog->program, prog->function_table,
					prog->strings, prog->variable_table, prog->inherit,
					prog->argument_types, prog->type_start));

	prog->program = (char *) DIFF(prog->program, prog);
	prog->function_table = (function_t *) DIFF(prog->function_table, prog);
	prog->function_flags = (unsigned short *) DIFF(prog->function_flags, prog);
	prog->strings = (char **) DIFF(prog->strings, prog);
	prog->variable_table = (char **) DIFF(prog->variable_table, prog);
	prog->variable_types = (unsigned short *) DIFF(prog->variable_types, prog);
	prog->inherit = (inherit_t *) DIFF(prog->inherit, prog);
	prog->classes = (class_def_t *) DIFF(prog->classes, prog);
	prog->class_members =
	        (class_member_entry_t *) DIFF(prog->class_members, prog);
	if (prog->type_start)
	{
		prog->argument_types =
		        (unsigned short *) DIFF(prog->argument_types, prog);
		prog->type_start = (unsigned short *) DIFF(prog->type_start, prog);
	}
	return 1;
}

/*
 * marion - relocate pointers after swap in
 *   program
 *   functions
 *   strings
 *   variable_names
 *   inherit
 *   argument_types
 *   type_start
 */
int locate_in(program_t * prog)
{
	if (!prog)
		return 0;
	prog->program = ADD(prog->program, prog);
	prog->function_table = (function_t *) ADD(prog->function_table, prog);
	prog->function_flags = (unsigned short *) ADD(prog->function_flags, prog);
	prog->strings = (char **) ADD(prog->strings, prog);
	prog->variable_table = (char **) ADD(prog->variable_table, prog);
	prog->variable_types = (unsigned short *) ADD(prog->variable_types, prog);
	prog->inherit = (inherit_t *) ADD(prog->inherit, prog);
	prog->classes = (class_def_t *) ADD(prog->classes, prog);
	prog->class_members =
	        (class_member_entry_t *) ADD(prog->class_members, prog);
	if (prog->type_start)
	{
		prog->argument_types =
		        (unsigned short *) ADD(prog->argument_types, prog);
		prog->type_start = (unsigned short *) ADD(prog->type_start, prog);
	}debug(d_flag, ("locate_in: %p %p %p %p %p %p %p\n",
					prog->program, prog->function_table,
					prog->strings, prog->variable_table, prog->inherit,
					prog->argument_types, prog->type_start));

	return 1;
}

/*
 * Swap out an object. Only the program is swapped, not the 'object_t'.
 *
 * marion - the swap seems to corrupt the function table
 */
int swap(object_t * ob)
{
	/* the simuls[] table uses pointers to the functions so the simul_efun
	 * program cannot be relocated.  locate_in() could be changed to
	 * correct this or simuls[] could use offsets, but it doesn't seem
	 * worth it just to get the simul_efun object to swap.  Maybe later.
	 *
	 * Ditto the master object and master_applies[].  Mudlibs that have
	 * a period TIME_TO_SWAP between successive master applies must be
	 * extremely rare ...
	 */
	if (ob == simul_efun_ob || ob == master_ob)
		return 0;
	if (ob->flags & O_DESTRUCTED
	)
		return 0;debug(d_flag, ("Swap object /%s (ref %d)", ob->name, ob->ref));

	if (ob->prog->line_info)
		swap_line_numbers(ob->prog); /* not always done before we get here */
	if ((ob->flags & O_HEART_BEAT) || (ob->flags & O_CLONE))
	{
		debug(d_flag, ("  object not swapped - heart beat or cloned."));
		return 0;
	}
	if (ob->prog->ref > 1 || ob->interactive)
	{
		debug(d_flag, ("  object not swapped - inherited or interactive or in apply_low() cache."));

		return 0;
	}
	if (ob->prog->func_ref > 0)
	{
		debug(d_flag, ("  object not swapped - referenced by functions."));

		return 0;
	}
	locate_out(ob->prog); /* relocate the internal pointers */
	if (swap_out((char *) ob->prog, ob->prog->total_size,
	        (int *) &ob->swap_num))
	{
		num_swapped++;
		free_prog(ob->prog, 0); /* Do not free the strings */
		ob->prog = 0;
		ob->flags |= O_SWAPPED;
		return 1;
	}
	else
	{
		locate_in(ob->prog);
		return 0;
	}
}

void load_ob_from_swap(object_t * ob)
{
	if (ob->swap_num == -1)
		fatal("Loading not swapped object.\n");

	debug(d_flag, ("Unswap object /%s (ref %d)", ob->name, ob->ref));

	swap_in((char **) &ob->prog, ob->swap_num);
	SET_TAG(ob->prog, TAG_PROGRAM);
	/*
	 * to be relocated: program functions strings variable_names inherit
	 * argument_types type_start
	 */
	locate_in(ob->prog); /* relocate the internal pointers */

	/* The reference count will already be 1 ! */
	ob->flags &= ~O_SWAPPED;
	num_swapped--;
	total_prog_block_size += ob->prog->total_size;
	total_num_prog_blocks += 1;
}

/*
 * Swap out line number information.
 */
int swap_line_numbers(program_t * prog)
{
	int size;

	if (!prog || !prog->line_info)
		return 0;

	debug(d_flag, ("Swap line numbers for /%s", prog->name));

	size = prog->file_info[0];
	if (swap_out((char *) prog->file_info, size, &prog->line_swap_index))
	{
		line_num_bytes_swapped += size;
		FREE(prog->file_info);
		prog->file_info = 0;
		prog->line_info = 0;
		return 1;
	}
	return 0;
}

/*
 * Reload line number information from swap.
 */
void load_line_numbers(program_t * prog)
{
	int size;

	if (prog->line_info)
		return;

	debug(d_flag, ("Unswap line numbers for /%s\n", prog->name));

	size = swap_in((char **) &prog->file_info, prog->line_swap_index);
	SET_TAG(prog->file_info, TAG_LINENUMBERS);
	prog->line_info = (unsigned char *) &prog->file_info[prog->file_info[1]];
	line_num_bytes_swapped -= size;
}

/**
 **  Misc. routines.
 **/

/*
 * Remove the swap space associated with this object.
 */
void remove_swap_file(object_t * ob)
{
	if (!ob)
		return;

	/* may be swapped out, so swap in to get size, update stats, etc */
	if (ob->flags & O_SWAPPED
	)
		load_ob_from_swap(ob);
	if (ob->prog)
		free_swap(ob->swap_num, ob->prog->total_size);
	ob->swap_num = -1;
}

/*
 * Same as above, but to remove line_number swap space.
 */
void remove_line_swap(program_t * prog)
{
	if (!prog->line_info)
		load_line_numbers(prog);
	if (prog->line_swap_index != -1 && prog->line_info)
		free_swap(prog->line_swap_index, prog->file_info[0]);
	prog->line_swap_index = -1;
}

void print_swap_stats(outbuffer_t * out)
{
	int size, cnt, end;
	sw_block_t *m;

	outbuf_add(out, "Swap information:\n");
	outbuf_add(out, "-------------------------\n");
	outbuf_addv(out, "Progs swapped:       %10lu\n", num_swapped);
	outbuf_addv(out, "Linenum bytes:       %10lu\n", line_num_bytes_swapped);
	outbuf_addv(out, "Total bytes swapped: %10lu\n", total_bytes_swapped);
	if (!swap_file)
	{
		outbuf_add(out, "No swap file\n");
		return;
	}
	size = cnt = 0;
	for (m = swap_free; m; size += m->length, cnt++, m = m->next)
		;
	swap_seek(0, 2);
#ifdef SWAP_USE_FD
	end = tell(swap_file) - last_data;
#else
	end = ftell(swap_file) - last_data;
#endif
	if (end)
	{
		size += end;
		cnt++;
	}
	outbuf_addv(out, "Freed bytes:         %10lu (%d chunks)\n", size, cnt);
}

/*
 * This one is called at shutdown. Remove the swap file.
 */
void unlink_swap_file()
{
	if (swap_file == 0)
		return;
#ifdef SWAP_USE_FD
	close(swap_file);
	unlink(file_name);
#else
	unlink(file_name); /* why is this backwards ? */
	fclose(swap_file);
#endif
}

